/*
 * Cherry programming language
 * Copyright (C) 2013 Christoph Mueller
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "cherry/parser.h"

#include <crystal/unicode.h>
#include <crystal/array.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

struct Mapping {
	byte_t* string;
	enum CyTOK value;
};


struct CyContext*   
cy_context_new(const byte_t* source, const char* filename)
{
	struct CyContext* context = malloc(sizeof(struct CyContext));
	struct CyFile* file = malloc(sizeof(struct CyFile));

	file->filename = filename;
	file->begin = source;
	context->src = (byte_t*) source;
	context->buffer = cry_array_new(sizeof(byte_t));
	context->file = file;

	return context;
}


struct CyContext*   
cy_context_repl_new(const byte_t* source)
{
	return cy_context_new(source, 0);
}


void
cy_context_free(struct CyContext* context)
{
	assert(context != 0);

	cry_array_free(context->buffer);

	free(context->file);
	free(context);
}

void                
cy_error(struct CyContext* context, const char* format, ...)
{
	va_list args;
	va_start(args, format);

	if(context->file != 0)
		fprintf(stderr, "ERROR %s", context->file->filename); 
	else
		fprintf(stderr, "ERROR");

	fprintf(stderr, ": ");
	vfprintf(stderr, format, args);

	va_end(args);
}

const byte_t*      
cy_tok_to_string(enum CyTOK token)
{
	switch(token) {
		case TOK_EOF:
			return "EOF";
		case TOK_COMMENT:
			return "COMMENT";
		case TOK_ROUNDLEFTBRACE:
			return "(";
		case TOK_ROUNDRIGHTBRACE:
			return ")";
		case TOK_SQUARELEFTBRACE:
			return "[";
		case TOK_SQUARERIGHTBRACE:
			return "]";
		case TOK_STRING:
			return "STRING";
		case TOK_DOT:
			return ".";
		case TOK_CHAR:
			return "CHAR";
		case TOK_HEX:
			return "HEX";
		case TOK_DEC:
			return "DEC";
		case TOK_OCT:
			return "OCT";
		case TOK_BIN:
			return "BIN";
		case TOK_FLOAT:
			return "FLOAT";
		case TOK_DEFINE:
			return "DEFINE";
		case TOK_IDENTIFIER:
			return "IDENTIFIER";
	}

	return "UNKNOWN";
}


enum FloatState {
	FP_START, 
	FP_EXPONENT, 
	FP_DOT, 
	FP_DECIMAL, 
	FP_MINUSPLUS, 
	FP_FINAL
};


static enum CyTOK
lex_float(struct CyContext* context)
{
	struct CryArray* buffer = context->buffer;
	byte_t* p = context->src;
	unicode_t ch = cry_utf8_get(p);

	enum FloatState state = FP_START;

	do {
		switch(state) {
			case FP_START:
				if(ch == 'e' || ch == 'E')
					state = FP_EXPONENT;
				else if(ch == '.')
					state = FP_DOT;
                else
                    assert(0);
				break;

			case FP_EXPONENT:
				if(ch == '+' || ch == '-')
					state = FP_MINUSPLUS;
				else if('0' > ch || ch > '9') {
					cy_error(context, "Unexpected character found in float literal after +/-");
					cry_array_append(buffer, "0", 1);
					goto RETURN_TOKEN;
				} else
					state = FP_FINAL;
				break;

			case FP_DOT:
				state = FP_DECIMAL;
				if('0' > ch || ch > '9') {
					cy_error(context, "Unexpected character found in float literal after dot");
					cry_array_append(buffer, "0", 1);
					goto RETURN_TOKEN;
				}
				break;

			case FP_DECIMAL:
				if(ch == 'e' || ch == 'E') 
					state = FP_EXPONENT;
				else if('0' > ch || ch > '9')
					goto RETURN_TOKEN;
				break;

			case FP_MINUSPLUS:
				state = FP_FINAL;
				if('0' > ch || ch > '9') {
					cy_error(context, "Unexpected character found in float literal");
					cry_array_append(buffer, "0", 1);
					goto RETURN_TOKEN;
				}
				break;

			case FP_FINAL:
				if('0' > ch || ch > '9')
					goto RETURN_TOKEN;
				break;
		}

		cry_array_append(buffer, p, 1);

NO_APPEND_BUFFER:
		p = cry_utf8_next(p);
		ch = cry_utf8_get(p);

	} while (ch != '\0');

RETURN_TOKEN:
	context->src = p;

	cry_array_append(buffer, "\0", 1);

	return TOK_FLOAT;
}


enum NumberState {
	INT_START, 
	INT_BASE, 
	INT_BIN_WAIT, 
	INT_HEX_WAIT, 
	INT_OCT_WAIT, 
	INT_BIN_READ, 
	INT_HEX_READ, 
	INT_OCT_READ, 
	INT_DEC_READ
};


static enum CyTOK
lex_number(struct CyContext* context)
{
	struct CryArray* buffer = context->buffer;
	byte_t* p = context->src;
	unicode_t ch = cry_utf8_get(p);

	enum CyTOK token;
	enum NumberState state = INT_START;

	do {
		switch(state) {
			case INT_START:
				if(ch == '0') 
					state = INT_BASE;
				else
					state = INT_DEC_READ;
				break;

			case INT_BASE:
				if(ch == 'x') {
					state = INT_HEX_WAIT;
				} else if(ch == 'b') {
					state = INT_BIN_WAIT;
				} else if('0' <= ch && ch <= '7') {
					state = INT_OCT_READ;
					break;
				} else if(ch == '.' || ch == 'e' || ch == 'E') {
					context->src = p;
					return lex_float(context);
				} else {
					token = TOK_DEC;
					goto RETURN_TOKEN;
				}
				break;

			case INT_HEX_WAIT:
				if(('0' > ch || ch > '9') && ('A' > ch || ch > 'F')) {
					cy_error(context, "Unexpected character found in hex literal");
					cry_array_append(buffer, "0", 1);
					token = TOK_HEX;
					goto RETURN_TOKEN;
				}
				state = INT_HEX_READ;
				break;

			case INT_BIN_WAIT:
				if(ch != '0' && ch != '1') {
					cy_error(context, "Unexpected character found in binary literal");
					cry_array_append(buffer, "0", 1);
					token = TOK_BIN;
					goto RETURN_TOKEN;
				}
				state = INT_BIN_READ;
				break;

			case INT_BIN_READ:
				token = TOK_BIN;
				if(ch == '_')
					goto NO_APPEND_BUFFER;
				else if(ch != '0' && ch != '1')
					goto RETURN_TOKEN;
				break;

			case INT_OCT_READ:
				token = TOK_OCT;
				if(ch == '_')
					goto NO_APPEND_BUFFER;
				else if('0' > ch || ch > '7')
					goto RETURN_TOKEN;
				break;

			case INT_DEC_READ:
				token = TOK_DEC;
				if(ch == '.' || ch == 'e' || ch == 'E') {
					context->src = p;
					return lex_float(context);
				} else if(ch == '_')
					goto NO_APPEND_BUFFER;
				else if('0' > ch || ch > '9')
					goto RETURN_TOKEN;
				break;

			case INT_HEX_READ:
				token = TOK_HEX;
				if(ch == '_')
					goto NO_APPEND_BUFFER;
				else if(('0' > ch || ch > '9') && ('A' > ch || ch > 'F'))
					goto RETURN_TOKEN;
				break;

			default:
				break;
		}

		cry_array_append(buffer, p, 1);

NO_APPEND_BUFFER:
		p = cry_utf8_next(p);
		ch = cry_utf8_get(p);
	} while(ch != '\0');

RETURN_TOKEN:
	context->src = p;
	cry_array_append(buffer, "\0", 1);

	return token;
}


enum CyTOK      
cy_lex(struct CyContext* context)
{
	assert(context != 0);

	byte_t* p = context->src;

	while(1) {
		cry_array_clear(context->buffer);

		unicode_t ch = cry_utf8_get(p);
		
		if(cry_unicode_isspace(ch)) {
			p = cry_utf8_next(p);
			context->src = p;
			continue;
		}

		switch(ch) {
			case '\0':
				return TOK_EOF;
			case '(':
				return TOK_ROUNDLEFTBRACE;
			case ')':
				return TOK_ROUNDRIGHTBRACE;
			case '[':
				return TOK_SQUARELEFTBRACE;
			case ']':
				return TOK_SQUARERIGHTBRACE;
			case '.':
				return TOK_DOT;
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				return lex_number(context);
			case '\\':
				// TODO: parsing
				return TOK_CHAR;
		}
	}

	return TOK_EOF;
}

byte_t*
cy_token_string(struct CyContext* context)
{
	assert(context->buffer != 0);

	(byte_t*) cry_array_get(context->buffer, 0);
}


size_t
cy_token_length(struct CyContext* context)
{
	assert(context->buffer != 0);

	cry_array_size(context->buffer);
}

